/* arch/arm/mach-goldfish/pdev_bus.c
**
** Copyright (C) 2007 Google, Inc.
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
*/

#include <xen/list.h>
#include <xen/irq.h>
#include <goldfish/head.h>
#include <asm-arm/errno-base.h>
#include <asm-arm/irq.h>
#include <asm/hardware.h>
#include <asm-arm/io.h>
#include <xen/sched.h>
#include <asm-arm/current.h>
#include <public/goldfish_drivers_types.h>
#include <goldfish/head.h>
#include <goldfish/goldfish_events.h>
#include <goldfish/goldfishfb.h>

#include <goldfish/goldfish.h>
#include <goldfish/drawchar.h>
//#include <xen/event.h>
	
#define PDEV_BUS_OP_DONE        (0x00)
#define PDEV_BUS_OP_REMOVE_DEV  (0x04)
#define PDEV_BUS_OP_ADD_DEV     (0x08)

#define PDEV_BUS_OP_INIT        (0x00)

#define PDEV_BUS_OP             (0x00)
#define PDEV_BUS_GET_NAME       (0x04)
#define PDEV_BUS_NAME_LEN       (0x08)
#define PDEV_BUS_ID             (0x0c)
#define PDEV_BUS_IO_BASE        (0x10)
#define PDEV_BUS_IO_SIZE        (0x14)
#define PDEV_BUS_IRQ            (0x18)
#define PDEV_BUS_IRQ_COUNT      (0x1c)

extern struct input_dev *input_dev;
extern unsigned int foreground_domain;

struct pdev_bus_dev {
	struct list_head list;
	struct platform_device pdev;
	struct resource resources[0];
};

static void goldfish_pdev_worker(struct work_struct *work);

static uint32_t pdev_bus_base;
static uint32_t pdev_bus_irq;
static LIST_HEAD(pdev_bus_new_devices);
static LIST_HEAD(pdev_bus_registered_devices);
static LIST_HEAD(pdev_bus_removed_devices);
//modify static DECLARE_WORK(pdev_bus_worker, goldfish_pdev_worker);


static void goldfish_pdev_worker(struct work_struct *work)
{
	int ret;
	struct pdev_bus_dev *pos, *n;

	list_for_each_entry_safe(pos, n, &pdev_bus_removed_devices, list) {
		list_del(&pos->list);
		//modify platform_device_unregister(&pos->pdev);
		//modify kfree(pos);
	}
	list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) {
		list_del(&pos->list);
		//modify 
		/*ret = platform_device_register(&pos->pdev);
		if(ret) {
			printk("goldfish_pdev_worker failed to register device, %s\n", pos->pdev.name);
		}
		else {
			printk("goldfish_pdev_worker registered %s\n", pos->pdev.name);
		}*/
		list_add_tail(&pos->list, &pdev_bus_registered_devices);
	}
}

static void goldfish_pdev_remove(void)
{
	struct pdev_bus_dev *pos, *n;
	uint32_t base;
	
	base = __raw_readl(pdev_bus_base + PDEV_BUS_IO_BASE);

	list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) {
		if(pos->resources[0].start == base) {
			list_del(&pos->list);
			xfree(pos);
			return;
		}
	}
	list_for_each_entry_safe(pos, n, &pdev_bus_registered_devices, list) {
		if(pos->resources[0].start == base) {
			list_del(&pos->list);
			list_add_tail(&pos->list, &pdev_bus_removed_devices);
			//modify schedule_work(&pdev_bus_worker);
			return;
		}
	};
	printk("goldfish_pdev_remove could not find device at %x\n", base);
}

static int goldfish_new_pdev(void)
{
	struct pdev_bus_dev *dev;
	uint32_t name_len;
	uint32_t irq = -1, irq_count;
	int resource_count = 2;
	uint32_t base;
	char *name;
	int alloc_size = 0;

	base = __raw_readl(pdev_bus_base + PDEV_BUS_IO_BASE);

	irq_count = __raw_readl(pdev_bus_base + PDEV_BUS_IRQ_COUNT);
	name_len = __raw_readl(pdev_bus_base + PDEV_BUS_NAME_LEN);
	if(irq_count)
		resource_count++;
	
	//, GFP_ATOMIC
	alloc_size = sizeof(*dev) + sizeof(struct resource) * resource_count + name_len + 1;
	//dev = xmalloc();
	dev = ((struct pdev_bus_dev *)_xmalloc(alloc_size, __alignof__(struct pdev_bus_dev)));	
	if(dev == NULL)
		return -ENOMEM;
	memset(dev,0,alloc_size);

	dev->pdev.num_resources = resource_count;
	dev->pdev.resource = (struct resource *)(dev + 1);
	dev->pdev.name = name = (char *)(dev->pdev.resource + resource_count);
	//modify dev->pdev.dev.coherent_dma_mask = ~0;

	__raw_writel(name, pdev_bus_base + PDEV_BUS_GET_NAME);
	name[name_len] = '\0';
	dev->pdev.id = __raw_readl(pdev_bus_base + PDEV_BUS_ID);
	dev->pdev.resource[0].start = base;
	dev->pdev.resource[0].end = base + __raw_readl(pdev_bus_base + PDEV_BUS_IO_SIZE) - 1;
	dev->pdev.resource[0].flags = IORESOURCE_MEM;
	if(irq_count) {
		irq = __raw_readl(pdev_bus_base + PDEV_BUS_IRQ);
		dev->pdev.resource[1].start = irq;
		dev->pdev.resource[1].end = irq + irq_count - 1;
		dev->pdev.resource[1].flags = IORESOURCE_IRQ;
	}

	printk("goldfish_new_pdev %s at %x irq %d,irq_count = %d,rc=%d\n", name, base, irq,irq_count,resource_count);
	list_add_tail(&dev->list, &pdev_bus_new_devices);
	//modify schedule_work(&pdev_bus_worker);
	//if(irq == 18)	
	if(!strcmp(name,"goldfish_events"))
		events_driver.probe(&(dev->pdev));
	
	//if(irq == 14 )
	if(!strcmp(name,"goldfish_fb"))
		goldfish_fb_driver.probe(&(dev->pdev));
	
	//if(irq == 15 )
	//if(!strcmp(name,"goldfish_audio"))
	//	goldfish_audio_driver.probe(&(dev->pdev));

	//if(irq == 16)
	if(!strcmp(name,"goldfish_mmc"))
		goldfish_mmc_driver.probe(&(dev->pdev));
	
	//if(irq == 10)//rtc
	//if(!strcmp(name,"goldfish_rtc"))
	//	goldfish_timer.probe(&(dev->pdev));
	
	//if(irq == 17)
	//if(!strcmp(name,"goldfish_battery"))
	//	goldfish_battery_device.probe(&(dev->pdev));

	//if(base == 0xff017000)
	if(!strcmp(name,"goldfish_nand"))
		goldfish_nand_driver.probe(&(dev->pdev));

	return 0;
}

irqreturn_t goldfish_pdev_bus_interrupt(int irq, void *desc,struct cpu_user_regs *regs)
{
	irqreturn_t ret = IRQ_NONE;
	int i = 0;
/*	static int init_once = 0;
	if(!init_once)
	{*/

	while(1) {
		uint32_t op = __raw_readl(pdev_bus_base + PDEV_BUS_OP);
		i++;
	//	init_once = 1;
		//printk("\\\\\\\\\\\\\\\\\i=%d\tinterrupt\\\\\\\\\\\\\\a\n",i);
		//if(i > 5)
		//	break;
		switch(op) {
			case PDEV_BUS_OP_DONE:
				return ret;

			case PDEV_BUS_OP_REMOVE_DEV:
				goldfish_pdev_remove();
				break;

			case PDEV_BUS_OP_ADD_DEV:
				goldfish_new_pdev();
				break;
		}
		ret = IRQ_HANDLED;
		}
	/*}
	else
	{
		if( !is_idle_vcpu(current) && init_once ==1 )
		{
			printk("xxxxxxxxxxxxxxsend_guest VIRQ_PDEV_BUS\n");
			send_guest_virq(current, VIRQ_PDEV_BUS);
			init_once++;
		}
		ret = IRQ_HANDLED;
	}*/

	return ret;
}

static int goldfish_pdev_bus_probe(struct platform_device *pdev)
{
	int ret;
	struct resource *r;
	r = platform_get_resource(pdev, IORESOURCE_IO, 0);
	if(r == NULL)
		return -EINVAL;
	pdev_bus_base = IO_ADDRESS(r->start);

	r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

	if(r == NULL)
		return -EINVAL;
	pdev_bus_irq = r->start;

	ret = request_irq(pdev_bus_irq, goldfish_pdev_bus_interrupt, IRQF_SHARED, "goldfish_pdev_bus", pdev);
	if(ret)
		goto err_request_irq_failed;

	__raw_writel(PDEV_BUS_OP_INIT, pdev_bus_base + PDEV_BUS_OP);
 	
err_request_irq_failed:
	return ret;
}

static int goldfish_pdev_bus_remove(struct platform_device *pdev)
{
	//modify , pdev
	//modify free_irq(pdev_bus_irq);
	return 0;
}

struct platform_driver goldfish_pdev_bus_driver = {
	.probe = goldfish_pdev_bus_probe,
	.remove = goldfish_pdev_bus_remove,
	//modify 
	/*.driver = {
		.name = "goldfish_pdev_bus"
	}*/
};


/*int bit_len[13]={
	BITS_TO_LONGS(EV_CNT),
	BITS_TO_LONGS(KEY_CNT),
	BITS_TO_LONGS(REL_CNT),
	BITS_TO_LONGS(ABS_CNT),
	BITS_TO_LONGS(MSC_CNT),
	BITS_TO_LONGS(LED_CNT),
	BITS_TO_LONGS(SND_CNT),
	BITS_TO_LONGS(FF_CNT),
	BITS_TO_LONGS(SW_CNT),
	ABS_MAX + 1,
	ABS_MAX + 1,
	ABS_MAX + 1,
	ABS_MAX + 1,
};


u32 offset[13]={
(u32)(((struct input_dev *)0)->evbit),
(u32)(((struct input_dev *)0)->keybit),
(u32)(((struct input_dev *)0)->relbit),
(u32)(((struct input_dev *)0)->absbit),
(u32)(((struct input_dev *)0)->mscbit),
(u32)(((struct input_dev *)0)->ledbit),
(u32)(((struct input_dev *)0)->sndbit),
(u32)(((struct input_dev *)0)->ffbit),
(u32)(((struct input_dev *)0)->swbit),
(u32)(((struct input_dev *)0)->absmax),
(u32)(((struct input_dev *)0)->absmin),
(u32)(((struct input_dev *)0)->absfuzz),
(u32)(((struct input_dev *)0)->absflat),
};
*/
#define EVBIT_OFFSET (u32)(((struct input_dev *)0)->evbit)
#define BIT_SIZE ((u32)&(((struct input_dev *)0)->keycodemax) - (u32)(((struct input_dev *)0)->evbit))
#define ABSMAX_OFFSET (u32)(((struct input_dev *)0)->absmax)
#define ABS_SIZE ((u32)&(((struct input_dev *)0)->open) - (u32)(((struct input_dev *)0)->absmax))
extern unsigned int fbdom_dmabase, fbdom_paddr;
int do_driver_init(int cmd, int count, void *devs_info)
{
	long rc;
	
	if( !is_idle_vcpu(current) )
	{
		switch ( cmd ) {
			case Driver_pdevbus_init:
				{
					struct pdev_bus_dev *pos, *n;
				int i = 0;
				struct dev_info *info = (struct dev_info *)devs_info;
				list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) {
					//list_del(&pos->list);
					if(i < count)
					{
						info->base = pos->pdev.resource[0].start;
						if(pos->pdev.num_resources == 3)
						{
							info->irq = pos->pdev.resource[1].start ;
							info->irq_count = pos->pdev.resource[1].end - pos->pdev.resource[1].start + 1;
						}
						info->id = pos->pdev.id;
						info->io_size = pos->pdev.resource[0].end - pos->pdev.resource[0].start + 1;
						//printk("info=0x%p,name=%s,base=0x%x\n",info,pos->pdev.name,info->base);
						memcpy(info->name,pos->pdev.name,strlen(pos->pdev.name));
						//can`t use "info = info + ++i " because of different version of gcc compiler
						info = (struct dev_info *)((u32)info+++i*sizeof(struct dev_info));
					}
				 }
				}
				rc = 0;
				break;

			case Driver_fb_status:
				//goldfishfb->status
				current->domain->status= (u32 *)devs_info;
				rc = 0;
				break;

			case Driver_fb_init:
				{
					struct fb_info_goldfish *info = (struct fb_info_goldfish *)devs_info;
				info->width = goldfishfb->fb.var.xres;
				info->height = goldfishfb->fb.var.yres;
				info->phys_width = goldfishfb->fb.var.width;
				info->phys_height = goldfishfb->fb.var.height;
				info->dma_base = goldfishfb->fb.screen_base;
				info->paddr = goldfishfb->fb.fix.smem_start;
				info->dom_dma_base = fbdom_dmabase;
				info->dom_paddr = fbdom_paddr;
				}
				rc = 0;
				break;

			case Driver_fb_fillrect:
				goldfishfb->fb.fbops->fb_fillrect(&goldfishfb->fb, (struct fb_fillrect *)devs_info);
				rc = 0;
				break;

			case Driver_fb_copyarea:
				goldfishfb->fb.fbops->fb_copyarea(&goldfishfb->fb, (struct fb_copyarea *)devs_info);
				rc = 0;
				break;

			case Driver_fb_imageblit:
				goldfishfb->fb.fbops->fb_imageblit(&goldfishfb->fb, (struct fb_image *)devs_info);
				rc = 0;
				break;
			
			case Driver_mmc_request:
			    mmc->ops->request(mmc,(struct mmc_request *)devs_info);	
				rc = 0;
				break;
			case Driver_event_init:
				{
					struct input_dev * guest_input_dev = (struct input_dev *)devs_info;
				/*	int i = 0;
					printk("EVBIT_OFFSET=%d,BIT_SIZE=%d,ABSMAX_OFFSET=%d,ABS_SIZE=%d\n",EVBIT_OFFSET,BIT_SIZE,ABSMAX_OFFSET,ABS_SIZE);*/
					memcpy((u32)devs_info+EVBIT_OFFSET,(u32)input_dev+EVBIT_OFFSET,BIT_SIZE);
					memcpy((u32)devs_info+ABSMAX_OFFSET+count,(u32)input_dev+ABSMAX_OFFSET,ABS_SIZE);
					guest_input_dev->keycodemax = input_dev->keycodemax;
					current->domain->scan_code = guest_input_dev->name;
				/*	for(;i < BITS_TO_LONGS(KEY_CNT) ; i++)
						printk("keybit[%d]=%d\n",i,input_dev->keybit[i]);*/
				}
				rc = 0;
				break;
			case Driver_timer_init:
				current->domain->cycles_alarm = (s64 *)devs_info;
				rc = 0;
				break;
			default:
				rc = -ENOSYS;
				break;
		}

	}
	return rc;
}

#define cr0 0
#define cr1 1
#define cr6 6
#define cr7 7
#define cr8 8
#define cr9 9
#define cr10 10

int do_android_op(int cmd, int count, u32 val)
{
	long rc;
	
	if( !is_idle_vcpu(current) )
	{
		switch ( cmd ) {
			case Android_vfp_fmrx:
				{
					switch(count)
					{
						case cr0:
							asm("mrc p10, 7, %0, cr0, cr0, 0 ": "=r" (rc) : : "cc");					
							break;

						case cr1:
							asm("mrc p10, 7, %0, cr1, cr0, 0 ": "=r" (rc) : : "cc");					
							break;
						case cr6:
							asm("mrc p10, 7, %0, cr6, cr0, 0 ": "=r" (rc) : : "cc");					
							break;

						case cr7:
							asm("mrc p10, 7, %0, cr7, cr0, 0 ": "=r" (rc) : : "cc");					
							break;
						case cr8:
							asm("mrc p10, 7, %0, cr8, cr0, 0 ": "=r" (rc) : : "cc");					
							break;

						case cr9:
							asm("mrc p10, 7, %0, cr9, cr0, 0 ": "=r" (rc) : : "cc");					
							break;
						case cr10:
							asm("mrc p10, 7, %0, cr10, cr0, 0 ": "=r" (rc) : : "cc");					
							break;

					}
				}
				break;
			case Android_vfp_fmxr:
			{
				switch(count)
				{
					case cr0:
						asm("mcr p10, 7, %0, cr0, cr0, 0 ": : "r" (val) : "cc");
						rc = 0;
						break;
					case cr1:
						asm("mcr p10, 7, %0, cr1, cr0, 0 ": : "r" (val) : "cc");
						rc = 0;
						break;
					case cr6:
						asm("mcr p10, 7, %0, cr6, cr0, 0 ": : "r" (val) : "cc");
						rc = 0;
						break;
					case cr7:
						asm("mcr p10, 7, %0, cr7, cr0, 0 ": : "r" (val) : "cc");
						rc = 0;
						break;
					case cr8:
						asm("mcr p10, 7, %0, cr8, cr0, 0 ": : "r" (val) : "cc");
						rc = 0;
						break;
					case cr9:
						asm("mcr p10, 7, %0, cr9, cr0, 0 ": : "r" (val) : "cc");
						rc = 0;
						break;
					case cr10:
						asm("mcr p10, 7, %0, cr10, cr0, 0 ": : "r" (val) : "cc");
						rc = 0;
						break;
				}
			}
			break;
			default:
				rc = -ENOSYS;
				break;
		}

	}
	return rc;
}

#define print 0
#define clear 1
extern u8* dom_bmp_start;
struct event_info event_screen_info = {5, 0, 1};
int do_screen_op(int cmd, void* bmp, void *extra_args)
{
	struct domain* dom0 = find_domain_by_domid(0);
	switch(cmd){
	case print:
	if(!bmp && dom0)
	{
		if(!virq_mask(dom0->vcpu[0], VIRQ_KEYBOARD)){
			memcpy(/*(void*)d->scan_code*/(void*)dom0->shared_info + 0xd00, (const void*)&event_screen_info, sizeof(struct event_info));
			send_guest_virq(dom0->vcpu[0], VIRQ_KEYBOARD);
		}
		return 0;
	}
		
	dom_bmp_start = (u8*)bmp;
        
	if(current->domain->domain_id == foreground_domain)
                print_foredomain("%s", (char*)extra_args);
	else
		print_backdomain("%s", (char*)extra_args);
        return 0;
	break;
	case clear:
	if(current->domain->domain_id == foreground_domain)
		clearfb_dom();
	else
		clear_dom_screen();	
	break;
	default:
	break;
	}
}

